%include "boot.inc"

[bits 16]
mov ax, 0xb800
mov gs, ax
mov ah, 0x03 ;青色
mov ecx, bootloader_tag_end - bootloader_tag
xor ebx, ebx
mov esi, bootloader_tag
output_bootloader_tag:	;bootloader程序运行提示信息
    mov al, [esi]
    mov word[gs:bx], ax
    inc esi
    add ebx,2
    loop output_bootloader_tag

;空描述符
mov dword [GDT_START_ADDRESS+0x00],0x00
mov dword [GDT_START_ADDRESS+0x04],0x00  

;创建描述符，这是一个数据段，对应0~4GB的线性地址空间
mov dword [GDT_START_ADDRESS+0x08],0x0000ffff    ; 基地址为0，段界限为0xFFFFF
mov dword [GDT_START_ADDRESS+0x0c],0x00cf9200    ; 粒度为4KB，存储器段描述符 

;建立保护模式下的堆栈段描述符      
mov dword [GDT_START_ADDRESS+0x10],0x00000000    ; 基地址为0x00000000，界限0x0 
mov dword [GDT_START_ADDRESS+0x14],0x00409600    ; 粒度为1个字节

;建立保护模式下的显存描述符   
mov dword [GDT_START_ADDRESS+0x18],0x80007fff    ; 基地址为0x000B8000，界限0x07FFF 
mov dword [GDT_START_ADDRESS+0x1c],0x0040920b    ; 粒度为字节

;创建保护模式下平坦模式代码段描述符
mov dword [GDT_START_ADDRESS+0x20],0x0000ffff    ; 基地址为0，段界限为0xFFFFF
mov dword [GDT_START_ADDRESS+0x24],0x00cf9800    ; 粒度为4kb，代码段描述符 

;初始化描述符表寄存器GDTR
mov word [pgdt], 39      ;描述符表的界限   
lgdt [pgdt]
      
in al,0x92                         ;南桥芯片内的端口 
or al,0000_0010B
out 0x92,al                        ;打开A20

cli                                ;中断机制尚未工作
mov eax,cr0
or eax,1
mov cr0,eax                        ;设置PE位
      
;以下进入保护模式
jmp dword CODE_SELECTOR:protect_mode_begin		;cs寄存器只能通过jmp改值

;16位的描述符选择子：32位偏移
;清流水线并串行化处理器
[bits 32]           
protect_mode_begin:                              

mov eax, DATA_SELECTOR                     ;加载数据段(0..4GB)选择子
mov ds, eax			;以下初始化各种段寄存器
mov es, eax
mov eax, STACK_SELECTOR
mov ss, eax
mov eax, VIDEO_SELECTOR
mov gs, eax

mov ecx, protect_mode_tag_end - protect_mode_tag	;用于保护模式提示信息输出
mov ebx, 80 * 2
mov esi, protect_mode_tag
mov ah, 0x3
output_protect_mode_tag:
    
    mov al, [esi]
    mov word[gs:ebx], ax
    add ebx, 2
    inc esi
    loop output_protect_mode_tag

mov ecx, end_char - id		; 保存字符串长度
mov esi, id			; 遍历字符串的指标
mov ebx, 160		; 屏幕坐标
mov ah, 0x3			; 字符颜色

global _main
_main:				
	mov dl,0			; 设置屏幕坐标的记录
	mov dh,1
    right_down:			; 右下角输出id字符串	
	call delay		; 延时函数
	inc dh			; 改变屏幕输出坐标, (rows++, cols++)			
	inc dl
	cmp dh,25		; 判断是否到达屏幕边缘并转跳
	jge right_up
	cmp dl,80
	jge left_down
	
	; 这里不采用循环输出的方式，而是采用递增的方式，因为循环输出会导致字符颜色改变
	; 由于现在是在保护模式下，所以可以直接使用gs段寄存器
	; 也因为在保护模式，所以不能用实模式的多种终端来设置变色函数和延时函数，但是只需稍作修改即可
	inc ah			; 改变字符状态值，达到变色效果
	mov al, [esi]		; 将输出字符的值
	mov word[gs:ebx], ax	; 在屏幕输出字符
	call double			; 双向输出函数
	add ebx,2			; 改变下一个输出点的坐标
	add ebx,160	

	inc esi			; 改变将输出的字符
	dec ecx			; 减少记录字符串长度寄存器
	cmp ecx,0		; 若字符输出完
	jle  right_down_re			; 进行循环输出的函数

	jmp right_down			; 循环
    right_down_re:				; 循环输出my字符串
	mov ecx, end_char-id	; 给指标值和记录字符串长度的寄存器重新赋值即可
	mov esi, id
	jmp right_down

	
    right_up:
	call delay
	dec dh
	inc dl
	cmp dh,0
	jle right_down
	cmp dl,83
	jge left_up

	inc ah
	mov al, [esi]
	mov word[gs:ebx], ax
	call double
	sub ebx,160
	add ebx,2

	inc esi
	dec ecx
	cmp ecx,0
	jle  right_up_re

	jmp right_up
    right_up_re:
	mov ecx, end_char-id
	mov esi, id
	jmp right_up

    left_up:
	call delay
	dec dh
	dec dl
	cmp dh,0
	jl left_down
	cmp dl,0
	jl right_up

	inc ah
	mov al, [esi]
	mov word[gs:ebx], ax
	call double
	sub ebx,160
	sub ebx,2

	inc esi
	dec ecx
	cmp ecx,0
	jle  left_up_re

	jmp left_up
    left_up_re:
	mov ecx, end_char-id
	mov esi, id
	jmp left_up
	
    left_down:
	call delay
	dec dl
	inc dh
	cmp dh,24
	jge left_up
	cmp dl,0
	jle right_down

	inc ah
	mov al, [esi]
	mov word[gs:ebx], ax
	call double
	add ebx,160
	sub ebx,2	
	
	inc esi
	dec ecx
	cmp ecx,0
	jle  left_down_re

	jmp left_down
    left_down_re:
	mov ecx, end_char-id
	mov esi, id
	jmp left_down
jmp $ ; 死循环

pgdt dw 0			;
     dd GDT_START_ADDRESS

bootloader_tag db 'run bootloader'
bootloader_tag_end:

protect_mode_tag db 'enter protect mode'
protect_mode_tag_end:

data:
	id db '22320131'
	end_char:

delay :					; 延时函数
	push ecx
	mov ecx, 1000000    ; 这里设置时间间隔
	waitf_loop:			; 利用loop递减ecx寄存器
	loop waitf_loop     ; ecx_ms
	pop ecx
	ret
double :					; 双向输出
	push edx				; 保存寄存器
	mov edx, 3998			; 4000-2
	sub edx, ebx			
	mov word[gs:edx], ax
	pop edx	
	ret			;返回
